/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syscalls.h>
#include <assert.h>

static const char hexlut[]
= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

/* FIXME: We temporarily initialize this to 0 instead of -1, so
 * that printf after fork works.  Cough.
 */
static int serial_handle = 0;

void
stdio_init(void)
{
  serial_handle = open("/dev/serial");
}

void
putchar(char ch)
{
  write(serial_handle, &ch, 1);
}

int
getchar(void)
{
  char ch;
  read(serial_handle, &ch, 1);
  return ch;
}

void
puts(const char *str)
{
  const char *p;

  for (p = str; *p != '\0'; p++)
    putchar(*p);

  putchar('\r');
  putchar('\n');
}

void
putp(const void *p)
{
  int i;

  putchar('0');
  putchar('x');

  for (i = 28 ; i >= 0 ; i -= 4) {
    putchar(hexlut[((uintptr_t)p >> i) & 0xf]);
  }
}

int
printf(const char * restrict format, ...)
{
  // TODO: Printf should naturally handle more than a 512 character buffer, this should
  // be solved at some point.
  char buffer[512];
  int char_count = 0;
  va_list ap;

  va_start(ap, format);
  vsnprintf(buffer, sizeof(buffer), format, ap);
  va_end(ap);

  char_count = write(serial_handle, buffer, strnlen(buffer, 512));
  return char_count;
}

int
snprintf(char *str, size_t size, const char * restrict format, ...)
{
  va_list ap;
  va_start(ap, format);
  int ret = vsnprintf(str, size, format, ap);
  va_end(ap);
  return ret;
}

static void
vsputch(char *str, size_t *n, size_t size, int c)
{
  if (*n < size) {
    str[*n] = c;
  }

  (*n) ++;
}

static int
vscmp(const char * restrict a, char *b)
{
  while (*a && *b) {
    if (*a != *b) return 0;
    a ++;
    b ++;
  }
  return 1;
}


int
vsnprintf(char *str, size_t size, const char * restrict format, va_list ap)
{
  // %[parameter][flags][width][.precision][length]type
  size_t n = 0;
  while (*format) {
    if (*format == '%') {
      format ++;

      int parameter = 1; // TODO: Param support
      long num = 0;
      bool zero_fill = false;
      bool always_sign = false;
      bool left_align = false;
      bool alternate_form = false;
      bool variable_pad = false;
      long prec_num = 0;
      bool variable_prec = false;
      // Flags
      bool flags_not_parsed = true;
      do {
        if (isdigit(*format)) {
          if (*format == '0') {
            zero_fill = true;
            format ++;
          } else {
            char * endp = NULL;
            num = strtol(format, &endp, 10);
            format = endp;
          }
        } else if (*format == '+') {
          always_sign = true;
          format ++;
        } else if (*format == '-') {
          left_align = true;
          format ++;
        } else if (*format == '#') {
          alternate_form = true;
          format ++;
        } else flags_not_parsed = false;
      } while (flags_not_parsed);

      // Width
      if (isdigit(*format)) {
        char * endp = NULL;
        num = strtol(format, &endp, 10);
        format = endp;
      } else if (*format == '*') {
        variable_pad = true;
        format ++;
      }

      // Precision
      if (*format == '.') {
        format ++;
        if (isdigit(*format)) {
          char * endp = NULL;
          prec_num = strtol(format, &endp, 10);
          format = endp;
        } else if (*format == '*') {
          variable_prec = true;
          format ++;
        } else {
          return -1;
        }
      }

      // Length and types
      if (vscmp(format, "hh")) {
        // Char promoted to int
        format += 2;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else if (*format == 'h') {
        // Short promoted to int
        format += 1;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else if (vscmp(format, "ll")) {
        // Long long
        format += 2;

        switch (*format) {
        case 'd':
        case 'i':
          return -1;
        case 'u': {
          unsigned long long val = va_arg(ap, unsigned long long);
          char tmp[20] = "";
          int i = 0;

          do {
            int digit = val % 10;
            val /= 10;
            tmp[i] = digit + '0';
            i ++;
            assert(i < 20);
          } while (val);
          for (int j = i-1 ; j >= 0 ; j --) {
            vsputch(str, &n, size, tmp[j]);
          }
          break;
        }
        default:
          return -1;
        }
        format ++;
      } else if (*format == 'l') {
        // Long
        format += 1;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else if (*format == 'L') {
        // Long Double
        format ++;
        switch (*format) {
        case 'f': // inf
        case 'F': // INF
        default:
          ;
        }
        format ++;
      } else if (*format =='z') {
        // Size_t
        size_t val = va_arg(ap, size_t);
        format += 1;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else if (*format == 'j') {
        // intmax_t
        format += 1;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else if (*format == 't') {
        // ptrdiff_t
        ptrdiff_t val = va_arg(ap, ptrdiff_t);
        format += 1;
        switch (*format) {
        case 'd':
        case 'i':
        case 'u':
        default:
          return -1;
        }
        format ++;
      } else { // No precision given
        // Type
        switch (*format) {
        case 'd': case 'i': {
          int val = va_arg(ap, int);
          unsigned int pval = (val < 0) ? -val : val;
          char tmp[20] = "";
          int i = 0;

          do {
            int digit = pval % 10;
            pval /= 10;
            tmp[i] = digit + '0';
            i ++;
          } while (pval);
          if (val < 0) vsputch(str, &n, size, '-');
          for (int j = i-1 ; j >= 0 ; j --) {
            vsputch(str, &n, size, tmp[j]);
          }
          format ++;
          break;
        }
        case 'u': {
          unsigned int val = va_arg(ap, unsigned int);
          char tmp[20] = "";
          int i = 0;

          do {
            int digit = val % 10;
            val /= 10;
            tmp[i] = digit + '0';
            i ++;
          } while (val);
          for (int j = i-1 ; j >= 0 ; j --) {
            vsputch(str, &n, size, tmp[j]);
          }
          format ++;
          break;
        }
        case 'f': case 'F':
          break;
        case 'e': case 'E':
          break;
        case 'g': case 'G':
          break;
        case 'x': {
          unsigned int val = va_arg(ap, unsigned int);
          vsputch(str, &n, size, '0');
          vsputch(str, &n, size, 'x');
          int i;
          for (i = 28 ; i >= 0 ; i -= 4) {
            vsputch(str, &n, size, hexlut[(val >> i) & 0xf]);
          }
          format ++;
          break;
        }
        case 'X': {
          unsigned int val = va_arg(ap, unsigned int);
          vsputch(str, &n, size, '0');
          vsputch(str, &n, size, 'X');
          int i;
          for (i = 28 ; i >= 0 ; i -= 4) {
            vsputch(str, &n, size, toupper(hexlut[(val >> i) & 0xf]));
          }
          format ++;
          break;
        }
        case 'o': break;
        case 's': {
          char *s = va_arg(ap, char*);
          while (*s) {
            vsputch(str, &n, size, *s);
            s ++;
          }
          format ++;
          break;
        }
        case 'c': {
          char ch = va_arg(ap, int);
          vsputch(str, &n, size, ch);
          format ++;
          break;
        }
        case 'p': {
          void *ptr = va_arg(ap, void*);
          vsputch(str, &n, size, '0');
          vsputch(str, &n, size, 'x');
          int i;
          for (i = 28 ; i >= 0 ; i -= 4) {
            vsputch(str, &n, size, hexlut[((uintptr_t)ptr >> i) & 0xf]);
          }
          format ++;
          break;
        }
        case 'n': {
          int *ptr = va_arg(ap, int*);
          *ptr = n;
          format ++;
          break;
        }
        case '%':
          vsputch(str, &n, size, *format);
          format ++;
          break;
        default:
          ;
        }
      }


    } else {
      vsputch(str, &n, size, *format);
      format ++;
    }
  }
  vsputch(str, &n, size, '\0');
  str[size-1] = '\0';
  return n;
}
